# -*- coding:utf-8 -*-

__author__='zhaoxp'

'''
这个例子主要演示了以下几个问题：
首先可以使用pickle这个包中各个dump,dumps,load,loads等方法对实例进行序列化，但是有几个问题会出现。
1 当类被修改，增加属性时，如果只是简单的从老的序列化对象从做load的话，新增的属性就不会出现，这时需要使用下面方法：
copy_reg.pickle(GameState,pickle_game_state)
另外，类的设置，对属性应该设置默认值。
2 当类被修改，属性被删除时，需要进行类版本控制，这时需要在pickle_game_state这种方法中做处理，下面有实例。
3 当类名被修改时，如果不做copy_reg.pickle(GameState,pickle_game_state)的处理，那么反序列化时就会出错。所以要在序列化前做
copy_reg.pickle(GameState,pickle_game_state)的操作。这时序列化后的对象中不包含类名信息，它可以被反序列化为其他类的实例。
'''

import pickle
import copy_reg


class GameState(object):
    def __init__(self):
        self.level = 0
        self.lives = 4

state = GameState()
state.level += 1
state.lives -= 1

state_path = './game_state.bin'
with open(state_path,'wb') as f:
    pickle.dump(state,f)
print('define GameState and call pickle.dump to dumpe into %s'%state_path)
print('')

with open(state_path, 'rb') as f:
    state_after = pickle.load(f)
print('load from %s'%state_path)
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('')

class GameState(object):
    def __init__(self):
        self.level = 0
        self.lives = 4
        self.points = 0

state_after = None
state = GameState()
serialized = pickle.dumps(state)
state_after = pickle.loads(serialized)
print('re-define GameState and add property(points).')
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('')

state_after = None
with open(state_path,'rb') as f:
    state_after = pickle.load(f)
print('load from old file which was dumped by pickle')
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('')


class GameState(object):
    def __init__(self, level=0, lives=4,points=0):
        self.level = level
        self.lives = lives
        self.points = points

state_after = None
with open(state_path,'rb') as f:
    state_after = pickle.load(f)
print('re-define GameSate with default values')
print('load from old file which was dumped by pickle')
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('no property(points) and need to find a way to load new property')
print('')

print('-'*50)
print('start to provide solution 1 to fix class change issue (add property)')
print('-'*50)
print('')

def pickle_game_state(game_state):
    kwargs = game_state.__dict__
    return unpickle_game_state,(kwargs,)

def unpickle_game_state(kwargs):
    return GameState(**kwargs)

copy_reg.pickle(GameState,pickle_game_state)

print('copy_reg GameSate with pickle_game_state')
state = GameState()
state.points += 1000
serialized = pickle.dumps(state)
state_after = pickle.loads(serialized)
print('unpickle GameState')
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('')

class GameState(object):
    def __init__(self, level=0, lives=4, points=0, magic=5):
        self.level = level
        self.lives = lives
        self.points = points
        self.magic = magic

print('re-define GameState and add property(magic)')
print('class GameState is now %s'%GameState)
state_after = pickle.loads(serialized)
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('')

print('-'*50)
print('start to provide solution 2 to fix class change issue (remove property)')
print('-'*50)
print('')

class GameState(object):
    def __init__(self, level=0, points=0, magic=5):
        self.level = level
        self.points = points
        self.magic = magic

print('re-define GameState and remove one property(lives)')
print('class GameState is now %s'%GameState)
state_after = None
try:
    state_after = pickle.loads(serialized)
    print('instance is %s'%state_after)
    print('state_after.__dict__ = %s'%state_after.__dict__)
    print('')
except TypeError as te:
    print('TypeError : %s'%te)
print('')


def pickle_game_state(game_state):
    kwargs = game_state.__dict__
    kwargs['version'] = 2
    return unpickle_game_state,(kwargs,)

def unpickle_game_state(kwargs):
    version = kwargs.pop('version',1)
    if version == 1:
        kwargs.pop('lives')
    return GameState(**kwargs)

copy_reg.pickle(GameState,pickle_game_state)
print('re-copy_reg GameState with  pickle_game_state')
state_after = pickle.loads(serialized)
print('instance is %s'%state_after)
print('state_after.__dict__ = %s'%state_after.__dict__)
print('no property lives')
print('')

print('-'*50)
print('start to provide solution 3 to fix class name change issue')
print('-'*50)
print('')

class MyState(object):
    def __init__(self):
        self.value = 10

my_state = MyState()
my_state.value += 13
serialized = pickle.dumps(my_state)
print('serialized = %s'%serialized)

del MyState
print('del MyState')
try:
    print('start to load from serialized str')
    my_state_after = pickle.loads(serialized)
    print('my_state_after = %s'%my_state_after)
except AttributeError as pe:
    print('AttributeError : %s'%pe)
print('')

class MyState(object):
    def __init__(self, value):
        self.value = value

class MyStateNew(object):
    def __init__(self, value):
        self.value = value

def pickle_game_state2(my_state):
    kwargs = my_state.__dict__
    return unpickle_game_state2,(kwargs,)

def unpickle_game_state2(kwargs):
    return MyStateNew(**kwargs)

copy_reg.pickle(MyState,pickle_game_state2)
my_state = MyState(1)
my_state.value += 13
serialized = pickle.dumps(my_state)
print('serialized = %s'%serialized)
print('there is no class name in serialized string')
print('')

del MyState
print('del MyState')
print('this shows serialized string could be loaded into object of new class')
my_state_after = pickle.loads(serialized)
print('my_state_after = %s'%my_state_after)



'''
if __name__=='__main__':
    print('Make pickle reliable with copyreg')
'''